#include <FileSystem/FAT32.h>
#include <FileSystem/FileAPI.h>
#include <IntExcept/Debug.h>
#include <IntExcept/Error.h>
#include <ProcessThread/ProcessThread.h>
#include <Utility/CLib.h>
#include <Memory/Heap.h>
#include <Kernel/GlobalData.h>
#include <Memory/Paging.h>
#include <FileSystem/Disk.h>

static LPCSTR split(LPSTR lpDst, LPCSTR lpSrc)
{
    while (*lpSrc != 0 && *lpSrc != '\\')
    {

        *lpDst = *lpSrc;
        lpDst++;
        lpSrc++;
    }
    *lpDst = 0;
    return (*lpSrc != 0) ? lpSrc + 1 : lpSrc;
}

static LPSTR join(LPSTR lpDst, LPCSTR lpSrc)
{
    int nDstLen = strlen(lpDst);
    while (nDstLen >= 0 && lpDst[--nDstLen] == '\\');
    lpDst[++nDstLen] = '\\';
    while (*lpSrc == '\\')
    {
        lpSrc++;
    }
    strcpy(lpDst + nDstLen + 1, lpSrc);
    return lpDst;
}


/*
@brief 解析 DOS 短目录项名称
@param `lpResult` 指向解析结果缓冲区的指针
@param `lpszName` 短目录项名称 `DIR_Name`
@return 解析结果长度
@note 当 `lpResult` 为 `NULL` 时，返回值为解析所需的缓冲区大小；否则，返回写入缓冲区的字符数量，包括 `'\0'`。
@note 短目录项名称为 11 字节，解析所得长度不会超过 13 字节，因此可跳过获取计算缓冲区大小的步骤，直接将缓冲区大小设为大于 13 字节。
*/
static int ResolveDOSShortDirName(LPSTR lpResult, const BYTE* lpszName)
{
    int i = 0;
    int j = 0;
    for (; i < 8; i++)
    {
        if (lpszName[i] != 0x20) /* 0x20 为填充值 */
        {
            lpResult[j++] = lpszName[i];
        }
    }
    int k = j; /* 保存扩展名点号所在位置 */
    lpResult[j++] = '\0'; /* 假定不存在扩展名 */
    BOOL bHasExtension = FALSE;
    for (; i < 11; i++)
    {
        if (lpszName[i] != 0x20)
        {
            bHasExtension = TRUE;
            lpResult[j++] = lpszName[i];
        }
    }
    if (bHasExtension)
    {
        lpResult[k] = '.';
        lpResult[j++] = '\0';
    }
    return j;
}

static int FindSubDir(LPCSTR lpszTarget, LPFAT32DIR lpDirEnt, DWORD dwEntCount)
{
    for (int i = 0; i < dwEntCount && lpDirEnt[i].DIR_Name[0] != 0x00; i++) /* 短文件名以 0x00 开头表示该项及之后均为空闲项目 */
    {
        char entryName[16];
        int len = ResolveDOSShortDirName(entryName, lpDirEnt[i].DIR_Name); /* 从目录项中解析出目录项名称 */
        if (len == 1 + strlen(lpszTarget) && 0 == strcmp(entryName, lpszTarget)) /* 先判断字符串长度是否相等，再检查字符串是否相等以提高效率 */
        {
            return i;
        }
    }
    return -1; /* 查找失败 */
}

/*
@brief 打开文件，创建 FCB
@param `lpszFileName` 文件路径
@param dwAttribute 采用何种方式创建
@return 文件 FCB
@note `lpszFileName` 必须具有如下格式：\foo\bar\baz.txt。
@note 必须是绝对路径，以 `\\` 开头，且相邻 token 之间的分隔符 `\\` 只能有 1 个。
@note 最后一个 token 后不允许跟 `\\`
*/
LPFileObject SimpleCreateFile(LPCSTR lpszFileName, DWORD dwAttributes)
{
    LPFileControlBlock lpMatch = MatchFCBByPath(lpszFileName);
    if (!lpMatch) return NULL; /* 匹配失败 */
    if (0 == strcmp(lpMatch->lpszPath, lpszFileName)) /* 完全匹配 */
    {
        LPFileObject lpRet = HeapAllocate(g_lpKernelHeap, HEAP_ZERO_MEMORY, sizeof(FileObject));
        lpMatch->dwReferences++;
        lpRet->lpFCB = lpMatch;
        lpRet->lpNext = GetCurrentProcess()->lpFileList;
        GetCurrentProcess()->lpFileList = lpRet;
        SetLastError(ERROR_SUCCESS);
        return lpRet;
    }
    /* path   = \foo\bar\baz.txt */
    /* match  = \foo */
    const char* lookahead = lpszFileName + strlen(lpMatch->lpszPath); /* 剩余未匹配的串 */
    DWORD dwPathLen = strlen(lpszFileName) + 1;
    LPSTR lpszTemp = HeapAllocate(g_lpKernelHeap, HEAP_ZERO_MEMORY, dwPathLen); /* 用于存放文件路径副本以处理字符串 */
    /* 分配一页空间，用于将目录文件调入内存 */
    LPFAT32DIR lpDirEnt = AllocatePageForKernel(1, PTE_WRITEABLE);
    DWORD dwErrorCode = ERROR_SUCCESS;
    while (*lookahead != '\0')
    {
        lookahead = split(lpszTemp, lookahead); /* 解析路径，得到子目录名 */
        if (*lpszTemp == '\0') continue; /* 跳过空串 */
        ReadDisk(lpDirEnt, lpMatch->dwFirstSector, 8); /* 读入目录簇 */
        int i = FindSubDir(lpszTemp, lpDirEnt, 4096); /* 在目录中查找目录项，返回索引 */
        if (i < -1) /* 未查找到目录项 */
        {
            dwErrorCode = ERROR_PATH_NOT_FOUND;
            break;
        }
        /* 找到子目录或文件，为其创建 FCB 缓存 */
        LPFileControlBlock lpNew = HeapAllocate(g_lpKernelHeap, HEAP_ZERO_MEMORY, sizeof(FileControlBlock));
        // lpMatch = HeapAllocate(g_lpKernelHeap, HEAP_ZERO_MEMORY, sizeof(FileControlBlock));
        lpNew->dwFileSize = lpDirEnt[i].DIR_FileSize;
        lpNew->dwFileType = lpDirEnt[i].DIR_Attr;
        lpNew->dwFirstCluster = lpDirEnt[i].DIR_FstClusLO + (lpDirEnt[i].DIR_FstClusHI << 16);
        // lpMatch->dwFirstSector = 8 * (lpMatch->dwFirstCluster - 2) /* 去掉 BPB 和 FAT 这两个簇 */
        //     + g_BPB.BPB_ResvdSecCnt /* BPB 占用的扇区数 */
        //     + (g_BPB.BPB_NumFATs * g_BPB.BPB_FATSz32); /* FAT 占用的扇区数 */
        lpNew->dwFirstSector = GetFirstSectorOfFAT32Cluster(&g_BPB, lpNew->dwFirstCluster);
        lpNew->kFileLock.bLockStatus = FALSE;
        lpNew->lpszPath = HeapAllocate(g_lpKernelHeap, HEAP_ZERO_MEMORY,
                strlen(lpMatch->lpszPath) + sizeof('\\') + strlen(lpszTemp) + sizeof('\0'));
        /* 与已匹配的路径拼接形成新路径 */
        strcpy(lpNew->lpszPath, lpMatch->lpszPath); /* \foo */
        join(lpNew->lpszPath, lpszTemp);
        /* 新创建的 FCB 加入链队 */
        lpNew->lpNext = g_FCBCache.lpNext;
        /* 引用计数 */
        lpNew->dwReferences = 0;
        g_FCBCache.lpNext = lpNew;
        lpMatch = lpNew;
        if (FCBIsDirectory(lpMatch) && *lookahead == '\0')
        {
            lpMatch = NULL;
            dwErrorCode = ERROR_ACCESS_DENIED;
            break;
        }
        else if (FCBIsDirectory(lpMatch) && *lookahead != '\0')
        {
            continue;
        }
        else if (!FCBIsDirectory(lpMatch) && *lookahead == '\0')
        {
            dwErrorCode = ERROR_SUCCESS;
            break;
        }
        else if (!FCBIsDirectory(lpMatch) && *lookahead != '\0')
        {
            lpMatch = NULL;
            dwErrorCode = ERROR_PATH_NOT_FOUND;
            break;
        }
    }
    /* 释放资源 */
    HeapFree(g_lpKernelHeap, lpszTemp);
    SetLastError(dwErrorCode);
    if (!lpMatch)
    {
        return NULL;
    }
    LPFileObject lpRet = HeapAllocate(g_lpKernelHeap, HEAP_ZERO_MEMORY, sizeof(FileObject));
    lpMatch->dwReferences++;
    lpRet->lpFCB = lpMatch;
    lpRet->dwCurrentCluster = lpMatch->dwFirstCluster;
    lpRet->lpNext = GetCurrentProcess()->lpFileList;
    GetCurrentProcess()->lpFileList = lpRet;
    return lpRet;
}